from __future__ import division

import math, os

from collections import OrderedDict
from datetime import datetime, timedelta

from xdh_logging import initLog
from settings import settings

log = initLog("lib")

STOP = r"-/<({[|STOP|]}}>\-"

def safe_str(obj):
	""" return the byte string representation of obj """
	try:
		return str(obj)
	except UnicodeEncodeError:
		# obj is unicode
		try:
			return str(obj.encode("utf-8"))
		except UnicodeEncodeError:
			unicode_text = unicode(obj).encode("unicode_escape")
			return str(unicode_text)


def safe_unicode(obj, *args):
	""" return the unicode representation of obj """
	try:
		return unicode(obj, *args)

	except UnicodeDecodeError:
		# obj is byte string
		try:
			return unicode(obj.decode("utf-8"))
		except UnicodeDecodeError:
			ascii_text = str(obj).encode('string_escape')
			return unicode(ascii_text)


def holidays(current = True):
	ret = set()
	now = datetime.today()

	for holiday, dates in settings.date.holiday.__dict__.iteritems():
		holiday = settings.tags.flags.values.__dict__[holiday]

		for start, end in dates:
			start = datetime.strptime("{date} {year}".format(date = start, year = now.year), "%b %d %Y")
			end = datetime.strptime("{date} {year}".format(date = end, year = now.year), "%b %d %Y") + timedelta(days = 1)
			if start <= now < end:
				ret.add(safe_unicode(holiday))
				break

	if current:
		return ret
	else:
		return set(holiday for holiday in settings.date.holiday.__dict__ if holiday not in ret)


def fix_filename(filename, test = False, reverse = False):
	"""
	Fixes a given filename. This will either add or remove the filepath found in settings.path.music_base
	depending on if reverse is set to False or True (respectively). if test is set to True, it will override
	this and do nothing.

	At the very least, it will set the filename to a unicode string, and will handle sql.TrackData or Track
	objects by only using their filename attributes.
	"""
	if "filename" in dir(filename):
		filename = filename.filename

	filename = safe_unicode(filename)

	if not reverse and not test and settings.path.music_base not in filename:
		log.debug(u'Adding "{path}/" to "{filename}".'.format(path = safe_unicode(settings.path.music_base), filename = safe_unicode(filename)))
		filename = os.path.join(settings.path.music_base, filename)

	elif reverse and not test and settings.path.music_base in filename:
		log.debug(u'Stripping "{path}/" from "{filename}".'.format(path = safe_unicode(settings.path.music_base), filename = safe_unicode(filename)))
		filename = filename[len(settings.path.music_base) + 1:]

	return safe_unicode(filename)

def favWeight(favorite):
	if favorite:
		mult = settings.data.fav_mult
	else:
		mult = settings.data.reg_mult

	total = settings.data.fav_mult + settings.data.reg_mult

	return mult / total


def subWeight(subType):
	total = 0
	for sub, data in settings.cat.sub.__dict__.iteritems():
		total += data.weight
		if sub == subType:
			mult = data.weight

	return mult / total

def sciRound(val):
	strVal = str(val).replace('.', '').replace('-', '').replace(',', '')
	trail = int(strVal[-1])

	if trail == 5:
		if len(strVal) > 1 and int(strVal[-2]) % 2 == 1:
			return math.ceil(val)
		else:
			return math.floor(val)
	else:
		return round(val)

def lastFMScrobbleTime(played, total):
	if total <= 30:
		return False

	four_min = 4 * 60

	scrobble_time = total / 2

	if scrobble_time > four_min:
		scrobble_time = four_min

	return played >= scrobble_time

def podLengthWeight(gmean, means):
	if len(means) == 0:
		return 0
	means = sorted(means)
	min_length = means[0]
	max_length = means[-1]

	length = (max_length + min_length) - gmean

	ttl_lengths = 0

	for item in means:
		ttl_lengths += (max_length + min_length) - item

	return length / ttl_lengths

def podCountWeight(count, counts):
	if len(counts) == 0:
		return 0
	ttl_counts = 0
	for item in counts:
		ttl_counts += item

	return count / ttl_counts

def podCatWeight(title, active_feeds):
	active_cats = OrderedDict()
	feed_cat = None
	for cat in settings.podcast.categories:
		feeds = [item.title for item in getattr(settings.podcast.pods, cat)]
		if title in feeds:
			feed_cat = cat

		active_cats[cat] = len(set(feeds) & set(active_feeds))
	
	if len(active_cats) == 0:
		return 0

	elif feed_cat == None:
		return 0

	ttl_weights = 0
	my_weight = len(active_cats) - active_cats.keys().index(feed_cat)

	for cat, value in active_cats.iteritems():
		ttl_weights += value * (len(active_cats) - active_cats.keys().index(cat))

	return my_weight / ttl_weights

def podFeedWeight(title, active_feeds):
	weights = []
	my_pos = None
	for items in settings.podcast.pods.__dict__.itervalues():
		feeds = [item.title for item in items]
		for i in xrange(len(feeds)):
			if i == len(weights):
				weights.append(1)
			else:
				weights[i] += 1

		if title in feeds:
			my_pos = feeds.index(title)

	if my_pos == None:
		return 0

	ttl_weights = 0
	my_weight = len(weights) - my_pos

	for i in xrange(len(weights)):
		ttl_weights += weights[i] * (len(weights) - 1)

	return my_weight / ttl_weights

def podWeight(cat, feed, count, length):
	return (cat + feed + count + length) / 4

def calcPodGap(weighted_length, num_pods, num_tracks):
	if weighted_length == 0 or num_pods == 0 or num_tracks == 0:
		return 0

	num_hours = 0 if datetime.today().strftime("%a") not in settings.podcast.days else len(settings.podcast.days[datetime.today().strftime("%a")])

	# Formula I've constructed to calculate an average gap size. The results are in seconds.

	return ((3600 * num_hours) / (num_pods) - weighted_length) / (math.log(num_tracks / (math.log(num_tracks) + 1)) + 1)

def podcastWeight(podcast_length, track_length, podcast_gap):
	if 0 in (podcast_length, track_length, podcast_gap):
		return 0

	date = datetime.today()
	num_hours = 0
	for hour in xrange(24):
		curr_date = date + timedelta(seconds = hour * 60 * 60)
		weekday = curr_date.strftime("%a")
		if weekday in settings.podcast.days and curr_date.hour in settings.podcast.days[weekday]:
			num_hours += 1

	if num_hours == 0:
		return 0

	return ((podcast_length * 9) / (podcast_length + math.ceil(podcast_gap / track_length) * track_length)) / (24)

def holidayWeight(holiday = None):
	hols = holidays()
	if len(hols) == 0 and holiday == None:
		return 1.0
	elif holiday == None:
		return settings.data.normal_mult / (settings.data.holiday_mult * len(hols) + settings.data.normal_mult)
	elif len(hols) == 0 or holiday not in hols:
		return 0.0
	else:
		return settings.data.holiday_mult / (settings.data.holiday_mult * len(hols) + settings.data.normal_mult)
	

def catWeight(cat):
	denom = 0
	numer = 0

	for weights in settings.cat.weight:
		denom += len(weights) * ((len(settings.cat.weight) + 1) - settings.cat.weight.index(weights))
		if cat in weights:
			numer = ((len(settings.cat.weight) + 1) - settings.cat.weight.index(weights))

	return numer / denom

def getMeans(values):
	count = len(values)
	if count == 0:
		return 0, 0, 0

	hTotal = 0
	aTotal = 0
        
	for value in values:
		hTotal = hTotal + (1 / value)
		aTotal = aTotal + (value)
        
	hMean = count / hTotal
        
	aMean = aTotal / count
        
	gMean = math.sqrt(aMean * hMean)

	return hMean, aMean, gMean

def calcTPD(total):
	return (24 * 60 * 60) / total

def minSize(tpd):
	return int(sciRound(tpd * settings.data.min_size))

def maxTracks(TPD, cat, sub, fav, num_tracks, holiday = False):
	
	if holiday:
		results = int(sciRound(TPD * holidayWeight(cat) * settings.data.list_const * favWeight(fav) * subWeight(sub)))
	else:
		results = int(sciRound(TPD * holidayWeight() * catWeight(cat) * settings.data.list_const * favWeight(fav) * subWeight(sub)))

	if results <= 0:
		results = 1

	if results > num_tracks:
		results = num_tracks

	if holiday:
		log.debug("int(sciRound({TPD} * {listConst} * {holidayWeight} * {favWeight} * {subWeight})) = {result}".format(TPD = TPD, holidayWeight = holidayWeight(cat), listConst = settings.data.list_const, favWeight = favWeight(fav), subWeight = subWeight(sub), result = results))
	else:
		log.debug("int(sciRound({TPD} * {listConst} * {holidayWeight} * {catWeight} * {favWeight} * {subWeight})) = {result}".format(TPD = TPD, holidayWeight = holidayWeight(), catWeight = catWeight(cat), listConst = settings.data.list_const, favWeight = favWeight(fav), subWeight = subWeight(sub), result = results))

	return results

